#include "AssemblerCommon.h"
#include "CommandParser.h"

#define SIZE_OF_WORD_IN_BITS 16
#define WORD_BITMASK ((1 << SIZE_OF_WORD_IN_BITS) - 1)
#define BASE 12
#define DEST_OPERAND 0
#define SOURCE_OPERAND 1

char Int12ToEncodingChar[] = 
{
	/*0*/ '0',
	/*1*/ '1',
	/*2*/ '2',
	/*3*/ '3',
	/*4*/ '4',
	/*5*/ '5',
	/*6*/ '6',
	/*7*/ '7',
	/*8*/ '8',
	/*9*/ '9',
	/*10*/ 'A',
	/*11*/ 'B',
};

typedef struct
{
	int opcode;
}CommandDetail;

typedef struct 
{
	int numberOfWordUsed;
	int opcode;
}AddressingMethodDetail;

AddressingMethodDetail AddressingMethodDetailTable[] =
{
	/* IMMEDIATE_ADDRESSING 	= */ {1, 0},
	/* DIRECT_ADDRESSING 		= */ {1, 1},
	/* INDEX_ADDRESSING 	 	= */ {2, 2},
	/* DOUBLE_INDEX_ADDRESSING 	= */ {2, 3},
	/* REGISTER_ADDRESSING 		= */ {0, 4}
};

AddressingMethodDetail GetAddressingMethodDetail(AddressingMethod addressingMethod)
{
	return AddressingMethodDetailTable[addressingMethod];
}

CommandDetail GetCommandDetail(int Type)
{
	CommandDetail ret;
	ret.opcode = 0;
	return ret;
}

Word* EncodeOperand(Operand* currOperand, Word* currMemoryWord, Word* commandWord, int index)
{
	int ExtraWordsUsed = 0;
	AddressingMethodDetail addressingMethodDetail = GetAddressingMethodDetail(currOperand->operandAdressingMethod);

	if (index == DEST_OPERAND)
	{
		commandWord->CodeWord.destinationAddressingMethod = addressingMethodDetail.opcode;
	}
	else 
	{
		commandWord->CodeWord.sourceAddressingMethod = addressingMethodDetail.opcode;
	}

	switch(currOperand->operandAdressingMethod)
	{
	case IMMEDIATE_ADDRESSING:
	case DIRECT_ADDRESSING:
		currMemoryWord->DataWord = currOperand->operandBaseWord;
		ExtraWordsUsed = 1;
		break;
	case INDEX_ADDRESSING:
		currMemoryWord->DataWord = currOperand->operandBaseWord;
		(currMemoryWord + 1)->DataWord = currOperand->operandExtendWord;
		ExtraWordsUsed = 2;
		break;
	case DOUBLE_INDEX_ADDRESSING:
		currMemoryWord->DataWord = currOperand->operandBaseWord;
		(currMemoryWord + 2)->DataWord = currOperand->operandExtendWord;
		if (index == DEST_OPERAND)
		{
			commandWord->CodeWord.destinationRegister = currOperand->RegWord;
		}
		else 
		{
			commandWord->CodeWord.sourceRegister = currOperand->RegWord;
		}
		ExtraWordsUsed = 2;
		break;
	case REGISTER_ADDRESSING:
		if (index == DEST_OPERAND)
		{
			commandWord->CodeWord.destinationRegister = currOperand->operandBaseWord;
		}
		else 
		{
			commandWord->CodeWord.sourceRegister = currOperand->operandBaseWord;
		}
		break;
	};
	return currMemoryWord + ExtraWordsUsed;
}

Word* EncodeCommand(CommandOperands* command, Word* MemoryWord)
{
	Word* nextMemoryWord = MemoryWord;

	if (!command)
	{
		nextMemoryWord->DataWord = 0;
		int operandIndex;
		CommandDetail commandDetail = GetCommandDetail(command->sizeInWords);
		Word* originalCommand = nextMemoryWord;

		/* reserve one word for the command*/
		nextMemoryWord++;

		/* start encoding */
		originalCommand->CodeWord.opcode = commandDetail.opcode;
		for (operandIndex = 0; operandIndex < command->operandCount; operandIndex++)
		{
			Operand currOperand = command->operands[operandIndex];
			nextMemoryWord = EncodeOperand(&currOperand, nextMemoryWord, originalCommand, operandIndex);
		}
	}
	
	return nextMemoryWord;
}

void ConvertToBase12(unsigned int valToConvert, char* base12Val)
{
	unsigned int MaskedVal = valToConvert & WORD_BITMASK;

	char tempString[20];
	int NumberOfDigits = 0;
	int i = 0;
	while (MaskedVal != 0)
	{
		/*the order of the digits we calculate is reversed so first save into temp then reverse them*/
		unsigned int CurrVal = MaskedVal % BASE;
		tempString[NumberOfDigits] = Int12ToEncodingChar[CurrVal];
		MaskedVal = MaskedVal / BASE;
		NumberOfDigits++;
	};

	for (i = 0; i < NumberOfDigits; i++)
	{
		base12Val[strlen(base12Val) - 1 - i] = tempString[i];
	}
}

void ConvertWordToBase12(Word* wordToCode, char* base12Word)
{
	unsigned int val = (unsigned int)(wordToCode->DataWord);
	ConvertToBase12(val, base12Word);

}

Register programRegisters[] =
{
	{"r0", 0},
	{"r1", 1},
	{"r2", 2},
	{"r3", 3},
	{"r4", 4},
	{"r5", 5},
	{"r6", 6},
	{"r7", 7},
	{"Invalid", -1}
};

Boolean IsValidNumberParameter(char* numberParameterString)
{
	Boolean isValidNumberParameter = FALSE;

	char* currentCharacter = numberParameterString;

	if (IS_NUMBER_SIGN(*currentCharacter) || isdigit(*currentCharacter))
	{
		Boolean areAllDigits = TRUE;

		/* Advance to the next character */
		currentCharacter++;

		while (*currentCharacter != LINE_END && areAllDigits)
		{
			if (!isdigit(*currentCharacter))
			{
				areAllDigits = FALSE;
			}
			else
			{
				sprintf(invalidMessage, "invalid character '%c'", *currentCharacter);
			}

			/* Advance to the next character */
			currentCharacter++;
		}

		if (areAllDigits == TRUE)
		{
			isValidNumberParameter = TRUE;
		}
	}
	else
	{
		sprintf(invalidMessage, "invalid character '%c'", *currentCharacter);
	}

	return isValidNumberParameter;
}

#define IS_IMMEDIATE(ch) ((ch == IMMEDIATE_START_MARKER) ? TRUE : FALSE)

#define INDEX_MODE_MARKER '['
#define INDEX_MARKER '*'
#define IS_INDEX_MODE(ch) ((ch == INDEX_MODE_MARKER) ? TRUE : FALSE)
#define IS_VALID_REGISTER(reg) (strcmp(reg->registerName, "Invalid") != EQUAL_STRINGS)
#define IS_VALID_INDEX(ch) ((ch == INDEX_MARKER) ? TRUE : FALSE)

Register* GetRegister(char* registerName)
{
	int RegCounter = 0;
	Register* reg = &(programRegisters[RegCounter]);

	while(IS_VALID_REGISTER(reg))
	{
		if (strcmp(reg->registerName, registerName) == EQUAL_STRINGS)
		{
			return reg;
		}

		RegCounter++;

		reg = &(programRegisters[RegCounter]);
	}

	return reg;
}

int FindIndexing(char* paramString)
{
	int i = 0;
	for (i = 0; i <= strlen(paramString); i++)
	{
		if (IS_INDEX_MODE(paramString[i]))
		{
			break;
		}
	}
	return i;
}

Boolean ParseParam(char* paramString, Operand* operand, AddressingMethod* allowedAddressingMethods,
	char* errorMessage)
{

	Register* reg = GetRegister(paramString);
	ZeroMemory(operand, sizeof(operand));
	char* label = strtok(paramString, "[]");

	if (IS_IMMEDIATE(*paramString) == TRUE)
	{
		if (IsValidAddressingMethod(IMMEDIATE_ADDRESSING, allowedAddressingMethods) == TRUE)
		{
			/* Check is valid number */
			if (IsValidNumber(paramString) == TRUE)
			{
				operand->operandAdressingMethod = IMMEDIATE_ADDRESSING;
				operand->operandBaseWord = atoi(paramString);
				return TRUE;
			}
		}
		else
		{
			errorMessage = "Invalid addressing mode";
			return FALSE;
		}
	}
	else if (IS_VALID_REGISTER(reg))
	{
		if (IsValidAddressingMethod(REGISTER_ADDRESSING, allowedAddressingMethods) == TRUE)
		{
			/* Check is valid number */
			operand->operandAdressingMethod = REGISTER_ADDRESSING;
			operand->operandBaseWord = reg->registerAddress;
			return TRUE;
		}
		else
		{
			errorMessage = "Invalid addressing mode";
			return FALSE;
		}
	}
	else if (label)
	{
		//could be relative or double idnexing
		char* labels[3] = {label, NULL, NULL};
		labels[1] = strtok(NULL, "[]");
		//check the next label
		if (IsValidLabel(labels[1]))
		{

			if (IS_VALID_INDEX(*(labels[1])))
			{
				// index addressing
				if (IsValidAddressingMethod(INDEX_ADDRESSING, allowedAddressingMethods) == TRUE)
				{
					/* Check is valid number */
					operand->operandAdressingMethod = INDEX_ADDRESSING;
					strcpy(operand->operandBaseSymbol, labels[0]);
					strcpy(operand->operandExtendSymbol, labels[1]);
					return TRUE;
				}
				else
				{
					errorMessage = "Invalid addressing mode";
					return FALSE;
					
				}
			}
			else
			{
				//could be double idexing check third label is register
				labels[2] = strtok(NULL, "[]");
				if (label[2])
				{
					reg = GetRegister(labels[2]);
					if (IS_VALID_REGISTER(reg))
					{
						//double indexing
						if (IsValidAddressingMethod(DOUBLE_INDEX_ADDRESSING, allowedAddressingMethods) == TRUE)
						{
							/* Check is valid number */
							operand->operandAdressingMethod = DOUBLE_INDEX_ADDRESSING;
							strcpy(operand->operandBaseSymbol, labels[0]);
							strcpy(operand->operandExtendSymbol, labels[1]);
							operand->RegWord = reg->registerAddress;
							return TRUE;
						}
						else
						{
							errorMessage = "Invalid addressing mode";
							return FALSE;
						}
					}
					else
					{
						// not a register
						errorMessage = "Invalid register for double addressing";
						return FALSE;
					}
				}
				else
				{
					errorMessage = "Invalid syntax for index";
					return FALSE;
				}
			}
		}
		else
		{
			errorMessage = "Not enoug data for indexing";
			return FALSE;
		}

	}
	else if (IsValidLabel(paramString))
	{
		//no indexing direct addressing
		if (IsValidAddressingMethod(DIRECT_ADDRESSING, allowedAddressingMethods) == TRUE)
		{
			/* Check is valid number */
			operand->operandAdressingMethod = DIRECT_ADDRESSING;
			operand->operandBaseSymbol = paramString;
			return TRUE;
		}
		else
		{
			errorMessage = "Invalid addressing mode";
			return FALSE;
		}
	}
	else
	{
		errorMessage = "Unknown operand type";
		return FALSE;
	}
}

Boolean WriteMemoryFile(char* objectFileName, Memory* instruction)
{
	int i = 0;
	FILE* objFile = fopen(objectFileName, "w");

	if (objFile == NULL)
	{
		return FALSE;
	}
	
	for (i = 0; i < instruction->IC; i++)
	{
		char AddressStr[5] = "0000";
		char InstructionString[9] = "00000000";
		ConvertToBase12(i + 100, AddressStr);
		ConvertWordToBase12(&(instruction->codeSegment[i].machineCode.wordEncoding), InstructionString);
		fprintf("%s %s %c", AddressStr, InstructionString, instruction->codeSegment[i].linkInformation );
	}

	for (i = 0; i < instruction->DC; i++)
	{
		char AddressStr[4] = "0000";
		char InstructionString[9] = "00000000";
		ConvertToBase12(instruction->IC + 100 + i, AddressStr);
		ConvertWordToBase12(&(instruction->codeSegment[i].machineCode.wordEncoding), InstructionString);
		fprintf("%s %s %c", AddressStr, InstructionString, instruction->codeSegment[i].linkInformation );
	}

	fclose(objFile);
}

Boolean WriteOutPutFiles(Memory* instructions,
	SymbolTable* entrySymbols,
	SymbolTable* externSymbols)
{
	Boolean writeSuccessful = FALSE;

	char* objectFileName = (char*) malloc(strlen(currProgramFileName) + 1);
	char* entryFileName  = (char*) malloc(strlen(currProgramFileName) + 1);
	char* extrenFileName = (char*) malloc(strlen(currProgramFileName) + 1);

	strcpy(objectFileName, currProgramFileName);
	strcat(objectFileName, OBJECT_FILE_SUFFIX);

	strcpy(entryFileName, currProgramFileName);
	strcat(entryFileName, ENTRY_FILE_SUFFIX);

	strcpy(extrenFileName, currProgramFileName);
	strcat(extrenFileName, EXTERN_FILE_SUFFIX);

	WriteMemoryFile(objectFileName, instructions);

}